﻿/*
 XsltDb is powerful XSLT module for DotnetNuke.
 It offers safe database access, SEO-friendly AJAX support,
 visitor interactions, environment integration (dnn properties,
 request, cookie, session and form values), regular expressions, etc.

 Author:
 
    Anton Burtsev
    burtsev@yandex.ru

 Project home page: 
 
    http://xsltdb.codeplex.com
*/

using System;
using System.Linq;
using System.Web.UI;
using System.Threading;
using System.Globalization;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Web;
using System.Web.Caching;

using System.Security;
using System.Net;
using System.Xml;
using System.Xml.Xsl;
using System.Xml.XPath;
using System.IO;
using System.Text;
using System.Data;
using System.Data.SqlClient;

using DotNetNuke.Entities.Modules;
using DotNetNuke.Entities.Portals;
using DotNetNuke.Entities.Tabs;
using DotNetNuke.Entities.Users;
using DotNetNuke.Common;
using DotNetNuke.Data;
using DotNetNuke.Services.Cache;
using DotNetNuke.Common.Utilities;
using DotNetNuke.Services.Scheduling;
//using DotNetNuke.Services.Exceptions;


namespace Findy.XsltDb
{
    /// <summary>
    /// Summary description for XsltDbUtils
    /// </summary>
    public class XsltDbUtils
    {
        public const string globalControlName = "XsltDbGlobals";

        static XsltDbUtils()
        {
            Regex.CacheSize = 300;
        }

        public class aConfig
        {
            public aConfig(string ConfigID, string XSLT, string Draft, bool IsSuper, string Name, string ServiceName)
            {
                this.ConfigID = ConfigID;
                this.XSLT = XSLT;
                this.Draft = Draft;
                this.IsSuper = IsSuper;
                this.Name = Name;
                this.ServiceName = ServiceName;
            }

            public aConfig(XPathNavigator c)
            {
                    this.ConfigID = c.Value("ConfigID");
                    this.XSLT = c.Value("XSLT");
                    this.Draft = c.Value("Draft");
                    this.IsSuper = Convert.ToBoolean(c.Value("IsSuper", "False"));
                    this.Name = c.Value("Name");
                    this.ServiceName = c.Value("ServiceName");
            }

            public string ConfigID;
            public string XSLT;
            public string Draft;
            public bool IsSuper;
            public string Name;
            public string ServiceName;

            public int ModuleID;
            public int TabID;
            public int TabModuleID;

            public string ActiveXslt
            {
                get {
                    UserInfo ui = DotNetNuke.Entities.Users.UserController.GetCurrentUserInfo();
                    bool useDraft = ui.IsInRole("Administrators") || ui.IsSuperUser;
                    return useDraft ? Draft : XSLT;
                }
            }
        }

        public const string configs_slot = "all_xsltdb_configs:";
        public static XPathNavigator CacheAllConfigs()
        {
            return (XPathNavigator)StaticCache2.Get(configs_slot, delegate()
            {
                return Data.ExecuteSQL(@"

select *, lower(ServiceName) ServiceNameLo from {databaseOwner}[{objectQualifier}Findy_XsltDb_Configs] order by Name;

select t.PortalID, t.TabID, m.ModuleID, tm.TabModuleID, c.ConfigID, c.Name, c.IsSuper, lower(c.ServiceName) ServiceNameLo
from {databaseOwner}[{objectQualifier}Findy_XsltDb_Configs] c
join {databaseOwner}[{objectQualifier}Findy_XsltDb_Modules] m on m.ConfigID = c.ConfigID
join {databaseOwner}[{objectQualifier}TabModules] tm on m.ModuleID = tm.ModuleID
join {databaseOwner}[{objectQualifier}Tabs] t on t.TabID = tm.TabID;

", "config, module", null);

            });
        }

        public const string settings_slot = "all_xsltdb_portal_settings:";
        public static XPathNavigator CacheAllSettings()
        {
            return (XPathNavigator)StaticCache2.Get(settings_slot, delegate()
            {
                return Data.ExecuteSQL("select * from {databaseOwner}[{objectQualifier}Findy_XsltDb_PortalSettings];", "settings", null);
            });
        }

        public static XPathNavigator GetConfigNodeByXPath(string xpath, params XPathVariable[] vars)
        {
            XPathExpression e = XPathExpression.Compile(xpath);
            XPathContext c = new XPathContext();
            foreach (XPathVariable v in vars)
                c.AddVariable(v);
            e.SetContext(c);
            XPathNavigator nav = CacheAllConfigs();
            return nav.SelectSingleNode(e);
        }
        public static aConfig GetConfigByXPath(string xpath, params XPathVariable[] vars)
        {
            XPathNavigator ConfigID_nav = GetConfigNodeByXPath(xpath, vars);
            if (ConfigID_nav == null)
                return null;
            return GetConfigByID(ConfigID_nav.Value);
        }
        public static aConfig GetConfigByID(string ConfigID)
        {
            XPathNavigator nav = CacheAllConfigs();
            XPathNavigator c = nav.SelectSingleNode("//config[ConfigID = '" + ConfigID + "']");

            if (c != null)
            {
                aConfig cfg = new aConfig(c);

                XPathNavigator mid = GetConfigNodeByXPath("//module[ConfigID = $cfg]",
                    new XPathVariable("cfg", cfg.ConfigID.ToString(), XPathResultType.String));
                if (mid != null)
                {
                    cfg.ModuleID = mid.SelectSingleNode("ModuleID").ValueAsInt;
                    cfg.TabID = mid.SelectSingleNode("TabID").ValueAsInt;
                    cfg.TabModuleID = mid.SelectSingleNode("TabModuleID").ValueAsInt;
                }
                return cfg;
            }
            return null;
        }

        private static aConfig GetConfigInternal(int ModuleID)
        {
            XPathNavigator mdl = GetConfigNodeByXPath("//module[ModuleID = $module]",
                new XPathVariable("module", ModuleID, XPathResultType.Number));
            if ( mdl != null )
            {
                XPathNavigator cfg = GetConfigNodeByXPath("//config[ConfigID = $cfg]",
                    new XPathVariable("cfg", mdl.SelectSingleNode("ConfigID").Value, XPathResultType.String));

                if ( cfg != null )
                {
                    aConfig c = new aConfig(cfg);
                    c.ModuleID = mdl.SelectSingleNode("ModuleID").ValueAsInt;
                    c.TabID = mdl.SelectSingleNode("TabID").ValueAsInt;
                    c.TabModuleID = mdl.SelectSingleNode("TabModuleID").ValueAsInt;
                    return c;
                }
            }
            return null;
        }

        public static aConfig GetConfig(int ModuleID)
        {
            return GetConfig(ModuleID, Null.NullInteger);
        }
        public static aConfig GetConfig(int ModuleID, int TabID)
        {
            if (ModuleID < 0)
                return null;

            ModuleInfo mi = new ModuleController().GetModule(ModuleID, TabID, false);
            if (mi == null) return null;

            if ( mi.DesktopModule.ModuleName == "XsltDb" )
                return GetConfigInternal(ModuleID);

            // TODO: I must use File dependency here instead caching for 2 seconds.
            return (aConfig)StaticCache2.Get("xsltdb:package:" + mi.ModuleControl.ControlSrc, 2, delegate() {
                string path = HttpContext.Current.Server.MapPath("~/" + mi.ModuleControl.ControlSrc);
                path = Path.GetDirectoryName(path);
                path = Path.Combine(path, "XsltDbLink.xml");

                XPathNavigator nav = new XPathDocument(path).CreateNavigator().SelectSingleNode("/XsltDbLink/Config");

                aConfig config = new aConfig(
                    string.Empty, //nav.SelectSingleNode("@id").Value,
                    nav.SelectSingleNode("@xslt").Value,
                    nav.SelectSingleNode("@xslt").Value,
                    bool.Parse(nav.SelectSingleNode("@is-super").Value),
                    nav.SelectSingleNode("@name").Value,
                    nav.SelectSingleNode("@alias").Value);
                return config;
            });

        }

        public static aConfig GetConfigByService(string ServiceName)
        {
            if (ServiceName.StartsWith("sys.", StringComparison.InvariantCultureIgnoreCase))
                return GetConfigByXPath("//module[ServiceNameLo = $service]/ConfigID",
                    new XPathVariable("service", ServiceName.ToLower(), XPathResultType.String));

            return GetConfigByXPath("//module[ServiceNameLo = $service and PortalID = $portal]/ConfigID",
                new XPathVariable("service", ServiceName.ToLower(), XPathResultType.String),
                new XPathVariable("portal", PortalController.GetCurrentPortalSettings().PortalId, XPathResultType.Number));
        }

        public static string GetXSLT(int ModuleID)
        {
            aConfig c = GetConfig(ModuleID);
            if (c == null)
                return string.Empty;

            UserInfo ui = DotNetNuke.Entities.Users.UserController.GetCurrentUserInfo();
            bool useDraft = ui.IsInRole("Administrators") || ui.IsSuperUser;
            return useDraft ? c.Draft : c.XSLT;
        }

        public static void AttachModuleToConfig(int ModuleID, string ConfigID)
        {
            Data.ExecuteNonQuery("Findy_XsltDb_AttachModuleToConfig", new object[] { ModuleID, ConfigID });
            StaticCache2.Clear(Transformer.xslCachePrefix);
            StaticCache2.Remove(configs_slot);
        }

        public static DataTable GetModulesWithSameConfig(int ModuleID)
        {
            DataTable dt = new DataTable("mod");
            using (IDataReader r = Data.ExecuteReader("Findy_XsltDb_GetModulesWithSameConfig", null, new object[]{ModuleID}))
                dt.Load(r);
            return dt;
        }

        public static string GetXSLTData(int ModuleID)
        {
            using (IDataReader r = Data.ExecuteReader("Findy_XsltDb_GetModuleData", null, new object[]{ModuleID}))
                if (r.Read())
                    return r[0].ToString();
            return "<xsltdb/>";
        }
        public static void ImportModule(int ModuleID, string data)
        {
            Data.ExecuteNonQuery("Findy_XsltDb_ImportModule", new object[]{ModuleID, data});
            StaticCache2.Clear(Transformer.xslCachePrefix);
            StaticCache2.Remove(configs_slot);
        }

        public static void SaveXSLT(int ModuleID, string xslt, bool isSuper, string Name, string ServiceName)
        {
            Data.ExecuteNonQuery("Findy_XsltDb_Save", new object[]{ModuleID, isSuper ? 1 : 0, xslt, Name, ServiceName});
            StaticCache2.Clear(Transformer.xslCachePrefix);
            StaticCache2.Remove(configs_slot);
        }
        public static void PublishAll()
        {
            Data.ExecuteNonQuery("Findy_XsltDb_PublishAll",
                new object[]{PortalController.GetCurrentPortalSettings().PortalId});
            StaticCache2.Clear(Transformer.xslCachePrefix);
            StaticCache2.Remove(configs_slot);
        }

        public static void Publish(int ModuleID)
        {
            Data.ExecuteNonQuery("Findy_XsltDb_Publish", new object[]{ModuleID});
            StaticCache2.Clear(Transformer.xslCachePrefix);
            StaticCache2.Remove(configs_slot);
        }

        public static DataTable GetModuleList(bool includeSuper)
        {
            string sql = @"
select
    m.ModuleID,
 t.TabName,
 case when len(t.Title) > 0 then t.Title else t.TabName end as TabTitle,
 {ModuleTitleTable}.ModuleTitle,
 tm.PaneName,
    t.TabID,
    c.Name as ConfigurationName,
    c.ServiceName as ServiceName,
case when XSLT<>Draft then 'Draft'
 else 'Published' end as Status,
 IsSuper
from {databaseOwner}[{objectQualifier}Findy_XsltDb_Modules] xslt
join {databaseOwner}[{objectQualifier}Findy_XsltDb_Configs] c on c.ConfigID = xslt.ConfigID
join {databaseOwner}[{objectQualifier}TabModules] tm on tm.ModuleID = xslt.ModuleID
join {databaseOwner}[{objectQualifier}Modules] m on m.ModuleID = xslt.ModuleID
join {databaseOwner}[{objectQualifier}Tabs] t on t.TabID = tm.TabID
where t.IsDeleted = 0 and m.Isdeleted = 0
and t.PortalID = {PortalID}";

            if ( ! includeSuper ) sql += "and c.IsSuper = 0";

            string moduleTitleTableNameSql = @"
select count(*) count from sys.columns where [object_id] = object_id('{databaseOwner}[{objectQualifier}Modules]')
and name = 'ModuleTitle';
";
            int count = Convert.ToInt32(Data.ExecuteSQL(moduleTitleTableNameSql, "c", null).SelectSingleNode("//count").Value);

            sql = sql
                .Replace("{PortalID}", PortalController.GetCurrentPortalSettings().PortalId.ToString())
                .Replace("{ModuleTitleTable}", count == 1 ? "m" : "tm");

            DataTable dt = new DataTable("xsltdb");
            using (IDataReader dr = Data.ExecuteSQL(sql,null))
                dt.Load(dr);

            return dt;
        }

        public static string GetModuleConfigID(int ModuleID, int TabID)
        {
            aConfig c = GetConfig(ModuleID);
            if ( c != null )
                return c.ConfigID;
            return string.Empty;
        }

        public static XsltDbPortalSettings GetPortalSettings(int PortalID)
        {
            XPathNavigator nav = CacheAllSettings();
            XPathNavigator setting = nav.SelectSingleNode("//settings[PortalID = " + PortalController.GetCurrentPortalSettings().PortalId + "]");
            if (setting == null) return null;

            XsltDbPortalSettings settings = new XsltDbPortalSettings();
            settings.UserName = setting.Value("xLogin");
            settings.DomainName = setting.Value("xDomain");
            settings.Password = setting.Value("xPassword");
            settings.ConnectionString = setting.Value("sqlConnectionString");

            settings.smtpServer = setting.Value("smtpServer");
            settings.smtpLogin = setting.Value("smtpLogin");
            settings.smtpPassword = setting.Value("smtpPassword");
            settings.noreplyEMail = setting.Value("noreplyEMail");

            return settings;
        }

        public static string GetExceptionMessage(Exception ex)
        {
            return HttpUtility.HtmlEncode(ex.ToString()).Replace(Environment.NewLine, "<br />");
        }

        public static MdoQueryString GetQSBySlot(string slot)
        {
            return new MdoQueryString(HttpContext.Current.Request[slot] ?? string.Empty);
        }
        public static MdoQueryString GetQSByClientID(string ClientID)
        {
            return new MdoQueryString(HttpContext.Current.Request[GetHidName(ClientID)] ?? string.Empty);
        }

        public static string GetHidName(string ClientID)
        {
            return "mdo-hid-" + ClientID + "-value";
        }
        public static CultureInfo GetSpecificCulture(string culture)
        {
            try
            {
                CultureInfo ci = CultureInfo.GetCultureInfo(culture);
                if (ci.IsNeutralCulture)
                    ci = CultureInfo.CreateSpecificCulture(culture);
                return ci;
            }
            catch { return null; }
        }
        public static void UpdateCulture()
        {
            HttpCookie language = HttpContext.Current.Request.Cookies["language"];
            if (language != null)
            {
                CultureInfo culture = GetSpecificCulture(language.Value);
                if (culture != null)
                {
                    Thread.CurrentThread.CurrentCulture = culture;
                    Thread.CurrentThread.CurrentUICulture = culture;
                }
                HttpContext.Current.Response.Cookies.Set(new HttpCookie("language", culture.Name));
            }
        }

        public static bool CurrentUserIsAuthorized(int TabID)
        {
            try
            {
                var tabs = new TabController().GetTabsByPortal(PortalController.GetCurrentPortalSettings().PortalId);
                if (!tabs.ContainsKey(TabID))
                    return false;
                var ti = tabs[TabID];
                var ui = UserController.GetCurrentUserInfo();
                foreach (DotNetNuke.Security.Permissions.TabPermissionInfo perm in ti.TabPermissions)
                    if (ui.IsInRole(perm.RoleName) || (ui.UserID != -1 && perm.UserID == ui.UserID))
                        return true;
            }
            catch { };
            return false;
        }

        internal static string GetCacheDescriptor(string procedure)
        {
            XPathNavigator cacheDescriptors = (XPathNavigator)StaticCache2.Get("xsltdb-xml-cache-descriptors", delegate()
            {
                return Data.ExecuteSQL(@"

select o.name, ep.value from sys.extended_properties ep
join sys.objects o on o.object_id = ep.major_id
where ep.name = 'XsltDb_CacheDescriptor'

", "descr", null);

            });

            procedure = DataProvider.Instance().ObjectQualifier + procedure;

            XPathExpression e = XPathExpression.Compile("//descr[name = $name]/value");
            XPathContext c = new XPathContext();
            c.AddVariable(new XPathVariable("name", procedure, XPathResultType.String));
            e.SetContext(c);

            XPathNavigator nav = cacheDescriptors.SelectSingleNode(e);

            if (nav == null) return null;
            return nav.Value;
        }

        public static void WriteLog(Exception ex)
        {
            new DotNetNuke.Services.Log.EventLog.ExceptionLogController().AddLog(ex);
        }
        public static void WriteLog(IDictionary<string, string> properties)
        {

            var log = new DotNetNuke.Services.Log.EventLog.LogInfo();
            log.AddProperty("URL", HttpContext.Current.Request.RawUrl);
            foreach ( var e in properties )
                log.AddProperty(e.Key, e.Value);
            log.LogTypeKey = Findy.XsltDb.Logging.EventLogType.XSLTDB_DATABASE_ACCESS.ToString();
            log.LogPortalID = PortalController.GetCurrentPortalSettings().PortalId;
            log.LogPortalName = PortalController.GetCurrentPortalSettings().PortalName;
            log.LogUserID = UserController.GetCurrentUserInfo().UserID;
            log.LogUserName = UserController.GetCurrentUserInfo().Username;

            var ctr = new DotNetNuke.Services.Log.EventLog.LogController();

            ctr.AddLog(log);
        }
    }

    public class XsltDbPortalSettings
    {
        public string UserName;
        public string DomainName;
        public string Password;
        public string ConnectionString;

        public string smtpServer;
        public string smtpLogin;
        public string smtpPassword;
        public string noreplyEMail;
    }

    public class Transformer
    {
        PortalModuleBase module = null;
        int mdoTabModuleID = -1;
        int mdoModuleID = -1;
        int mdoTabID = -1;
        int mdoPortalID = -1;
        int mdoUserID = -1;

        public Control TrueHost;
        public XsltDbUtils.aConfig Config;

        public Transformer(PortalModuleBase module):this(module, -1){ }
        public Transformer(PortalModuleBase module, int ModuleID)
        {
            if (module != null)
            {
                this.module = module;
                mdoModuleID = module.ModuleId;
                mdoTabModuleID = module.TabModuleId;
                mdoTabID = module.TabId;
                mdoPortalID = module.PortalId;
                mdoUserID = module.UserId;
            }
            else
            {
                mdoUserID = UserController.GetCurrentUserInfo().UserID;
                mdoTabID = TabController.CurrentPage.TabID;
                mdoPortalID = PortalController.GetCurrentPortalSettings().PortalId;
                mdoModuleID = ModuleID;
            
            }
            dnnSettings = new theDnnSettings(mdoModuleID, mdoUserID, mdoPortalID, mdoTabID, mdoTabModuleID, this);
        }
        public Transformer(int mdoTabModuleID, int mdoModuleID, int mdoTabID, int mdoPortalID)
        {
            this.mdoUserID = UserController.GetCurrentUserInfo().UserID;
            this.mdoPortalID = mdoPortalID;
            this.mdoTabID = mdoTabID;
            this.mdoModuleID = mdoModuleID;
            this.mdoTabModuleID = mdoTabModuleID;
            dnnSettings = new theDnnSettings(mdoModuleID, mdoUserID, mdoPortalID, mdoTabID, mdoTabModuleID, this);
        }
        public Transformer(int mdoTabModuleID, int mdoModuleID, int mdoTabID)
        {
            this.mdoUserID = UserController.GetCurrentUserInfo().UserID;
            PortalSettings ps = PortalController.GetCurrentPortalSettings();
            this.mdoPortalID = ps != null ? ps.PortalId : -1;
            this.mdoTabID = mdoTabID;
            this.mdoModuleID = mdoModuleID;
            this.mdoTabModuleID = mdoTabModuleID;
            dnnSettings = new theDnnSettings(mdoModuleID, mdoUserID, mdoPortalID, mdoTabID, mdoTabModuleID, this);
        }

        public class theDnnSettings
        {
            private ModuleInfo m = null;
            private UserInfo u = null;
            private PortalInfo p = null;
            private TabInfo t = null;
            Transformer transformer = null;

            private int ModuleID;
            private int UserID;
            private int PortalID;
            private int TabID;
            private int TabModuleID;

            public theDnnSettings(int ModuleID, int UserID, int PortalID, int TabID, int TabModuleID, Transformer t)
            {
                this.TabID = TabID;
                this.ModuleID = ModuleID;
                this.PortalID = PortalID;
                this.UserID = UserID;
                transformer = t;
            }

            public ModuleInfo M { get {
                if (m == null && ModuleID >= 0)
                {
                    if ( TabModuleID > 0 )
                        m = new ModuleController().GetModule(ModuleID, TabID, false);
                    else
                        m = new ModuleController().GetModule(transformer.Config.ModuleID, transformer.Config.TabID, false);
                }
                return m;
            } }

            public UserInfo U { get {
                if (u == null && UserID >= 0)
                    u = UserController.GetCurrentUserInfo();
                return u;
            } }

            public PortalInfo P { get {
                if (p == null)
                {
                    if (PortalID >= 0)
                        p = new PortalController().GetPortal(PortalID);
                    else
                        p = new PortalController().GetPortal(PortalController.GetCurrentPortalSettings().PortalId);
                }
                return p;
            } }

            public TabInfo T { get {
                if (t == null)
                {
                    if (TabID >= 0)
                        t = new TabController().GetTab(TabID, P.PortalID, false);
                    else
                        t = TabController.CurrentPage;
                }
                return t;
            } }
        }
        theDnnSettings dnnSettings = null;

        public theDnnSettings DnnSettings
        {
            get { return dnnSettings; }
        }

        internal int TabModuleID
        {
            get
            {
                if (module != null)
                    return module.TabModuleId;
                return mdoTabModuleID;
            }
        }

        internal int ModuleID
        {
            get
            {
                if (module != null)
                    return module.ModuleId;
                return mdoModuleID;

            }
        }

        internal int TabID
        {
            get
            {
                if (module != null)
                    return module.TabId;
                if ( mdoTabID > 0 )
                    return mdoTabID;
                if (TabController.CurrentPage != null)
                    return TabController.CurrentPage.TabID;
                return -1;
            }
        }

        internal int PortalID
        {
            get
            {
                if (module != null)
                    return module.PortalId;
                if ( mdoPortalID >= 0 )
                    return mdoPortalID;
                return PortalController.GetCurrentPortalSettings().PortalId;
            }
        }

        internal int UserID
        {
            get
            {
                if (module != null)
                    return module.UserId;
                if ( mdoUserID > 0 )
                    return mdoUserID;
                return UserController.GetCurrentUserInfo().UserID;
            }
        }

        public PortalModuleBase Module
        {
            get { return module; }
        }

        public bool IsNavigate
        {
            get
            {
                if (module == null && TrueHost == null)
                    return false;
                if (TrueHost != null)
                    return !TrueHost.Page.IsPostBack;
                return !module.Page.IsPostBack;
            }
        }

        public bool IsAjax
        {
            get { return HttpContext.Current != null && HttpContext.Current.Handler is IXsltDbAjax; }
        }

        public bool IsService
        {
            get { return HttpContext.Current != null && HttpContext.Current.Handler is IXsltDbService; }
        }

        public bool IsPostBack
        {
            get
            {
                if (module == null && TrueHost == null)
                    return false;
                if (TrueHost != null)
                    return TrueHost.Page.IsPostBack;
                return module.Page.IsPostBack;
            }
        }

        public bool IsSystem
        {
            get { return TabID == -1; }
        }


        private const string xslLargeHeaderFooter = @"
<xsl:stylesheet version=""2.0""
   xmlns:xsl=""http://www.w3.org/1999/XSL/Transform""
   xmlns:msxsl=""urn:schemas-microsoft-com:xslt""
   exclude-result-prefixes=""msxsl mdo script""
   xmlns:mdo=""urn:mdo""
   xmlns:script=""urn:script""
   {2}
>
  <xsl:output method=""{0}"" omit-xml-declaration=""yes"" encoding=""UTF-8"" indent=""no""/>

  <xsl:template match=""/"">

{1}

  </xsl:template>

</xsl:stylesheet>
";

        private const string xslSmallHeaderOutput = @"<xsl:output method=""{0}""  omit-xml-declaration=""yes"" indent=""no"" />";


        private const string xslSmallHeaderFooter = @"
<xsl:stylesheet version=""2.0""
   xmlns:xsl=""http://www.w3.org/1999/XSL/Transform""
   xmlns:msxsl=""urn:schemas-microsoft-com:xslt""
   exclude-result-prefixes=""msxsl mdo script""
   xmlns:mdo=""urn:mdo""
   xmlns:script=""urn:script""
   {1}
>

{0}

</xsl:stylesheet>
";
        internal static Regex TemplateParserRegex = new Regex(@"<\s*mdo:template\s+name\s*=\s*""([^""]+?)""\s*>(.*?)<\s*/\s*mdo:template\s*>", RegexOptions.Singleline | RegexOptions.Compiled);
        internal static Regex ClientXslRegex = new Regex(@"<\s*mdo:client-xsl\s+js\s*=\s*""([^\(]+)\(([^\)]*)\)\s*""\s*>(.*?)<\s*/\s*mdo:client-xsl\s*>", RegexOptions.Singleline | RegexOptions.Compiled);
        internal static Regex SetupRegex = new Regex(@"<\s*mdo:setup\s*>(.*?)<\s*/\s*mdo:setup\s*>", RegexOptions.Singleline | RegexOptions.Compiled);
        internal static Regex SearchableRegex = new Regex(@"<\s*mdo:searchable\s*>(.*?)<\s*/\s*mdo:searchable\s*>", RegexOptions.Singleline | RegexOptions.Compiled);
        internal static Regex PreRenderRegex = new Regex(@"<mdo:pre-render>(.*?)</mdo:pre-render>", RegexOptions.Singleline | RegexOptions.Compiled);

        internal static Regex HeaderRegex = new Regex(@"<\s*(mdo(:|\.)header|noajax)(\s+(?<a>(?<n>[\w:\.-]+)\s*=\s*""(?<v>[^""]+)""\s*)+)?>(?<b>.*?)<\s*/\s*(mdo(:|\.)header|noajax)\s*>", RegexOptions.Singleline | RegexOptions.Compiled);
        internal static Regex NoAjaxRegex = new Regex(@"<noajax>.*?</noajax>", RegexOptions.Singleline | RegexOptions.Compiled);

        internal static Regex ImportableRegex = new Regex(@"<\s*mdo:importable\s+name\s*=\s*""([^""]+)""\s*>(.*?)<\s*/\s*mdo:importable\s*>", RegexOptions.Singleline | RegexOptions.Compiled);
        internal static Regex ServiceRegex = new Regex(@"<\s*mdo:service\s+(?<a>(?<n>name|type|secure)\s*=\s*""(?<v>[^""]+)""\s*)+>(?<b>.*?)<\s*/\s*mdo:service\s*>", RegexOptions.Singleline | RegexOptions.Compiled);
        internal static Regex CallableRegex = new Regex(@"<\s*(mdo:)?callable\s+js\s*=\s*""([^\(]+)\(([^\)]*)\)\""(\s*type\s*=\s*"".+?"")?\s*>(.*?)<\s*/\s*(mdo:)?callable\s*>", RegexOptions.Singleline | RegexOptions.Compiled);

        internal static Regex jsHandlerRegex = new Regex(@"\#\#handler\s*:\s*([^\(]+)\(([^\)]*)\)", RegexOptions.Singleline | RegexOptions.Compiled);
        internal static Regex svcContentTypeRegex = new Regex(@"\#\#service-content-type\s+(\S+?)[\n\r]+", RegexOptions.Singleline | RegexOptions.Compiled);
        internal static Regex liveModuleRegex = new Regex(@"\#\#live\s*(\d+)?\s*(smart)?", RegexOptions.Singleline | RegexOptions.Compiled);

        internal static Dictionary<string, string> GetXmlTagFromMatch(Match m)
        {
            var attrs = new Dictionary<string, string>();
            for (var i = 0; i < m.Groups["a"].Captures.Count; i++)
                attrs[m.Groups["n"].Captures[i].Value] = m.Groups["v"].Captures[i].Value;
            return attrs;
        }

        public void PreRender(string xsl, string xml, bool IsSuper)
        {
            if (xsl.Trim().Length == 0) return;

            var m = PreRenderRegex.Match(xsl);
            if (m.Success)
                JustTransform(m.Groups[1].Value, xml, false, IsSuper);
        }

        public string GetSettingsForm(string xsl, bool IsSuper)
        {
            if (xsl.Trim().Length == 0) return string.Empty;

            var m = SetupRegex.Match(xsl);
            if (m.Success)
                return JustTransform(m.Groups[1].Value, "<root/>", false, IsSuper);
            return string.Empty;
        }

        public string ExecuteSearch(string xsl, bool IsSuper)
        {
            if (xsl.Trim().Length == 0) return "<root/>";

            var m = SearchableRegex.Match(xsl);
            if (m.Success)
                return JustTransform(m.Groups[1].Value, "<root/>", false, IsSuper);
            return "<root/>";
        }

        public string ExecuteService(string xsl, string xml, string serviceName, bool IsSuper, int TabID)
        {
            var type = "text/xml";
            if (xsl.Trim().Length == 0) return string.Empty;

            foreach (Match m in ServiceRegex.Matches(xsl))
            {
                var attrs = GetXmlTagFromMatch(m);

                if (string.Compare(HttpUtility.HtmlDecode(attrs["name"]), serviceName, true) == 0)
                {
                    if (attrs.ContainsKey("secure") && attrs["secure"] == "yes")
                        if (!XsltDbUtils.CurrentUserIsAuthorized(TabID))
                            throw new SecurityException("the current XsltDbService is secure buth the current user is not authorized to access it.");

                    if (attrs.ContainsKey("type"))
                        type = attrs["type"];
                    if (HttpContext.Current.Request.Params["content-type"] != null)
                        type = HttpContext.Current.Request.Params["content-type"];
                    HttpContext.Current.Response.ContentType = type;
                    return JustTransform(m.Groups["b"].Value, xml, false, IsSuper);
                }
            }

            return string.Empty;
        }

        public string JustTransform(string xsl, string xml, bool debug, bool IsSuper)
        {
            var sw = Stopwatch.StartNew();

            xsl = PrepareXslt(xsl);

            var h = new Helper(this);
            var html = string.Empty;

            if (debug)
                html = TransformDebug(xml, xsl, h, html, IsSuper);
            else
                html = TransformRelease(xml, xsl, h, html, IsSuper);

            var res = h.getWatchers() + html;

            sw.Stop();

            //Uncomment this to start collecting performance information
            //SaveElapsed(sw.ElapsedMilliseconds);

            return res;
        }

        private void SaveElapsed(long elapsed)
        {
            List<DataTable> timesContainer = (List<DataTable>)StaticCache2.Get("xsltdb-profiler", delegate()
            {
                return new List<DataTable>();
            });
            lock (timesContainer)
            {
                if (timesContainer.Count == 0)
                    timesContainer.Add(CreateDT());
                DataTable times = timesContainer[0];
                times.Rows.Add(new object[] { this.TabModuleID, elapsed, DateTime.Now });
                if (times.Rows.Count > 1000)
                {
                    StringWriter sw = new StringWriter();
                    times.WriteXml(sw);
                    timesContainer.Clear();
                    Data.ExecuteNonQuery("Findy_XsltDb_SaveLog", new object[]{sw.ToString()});
                }
            }
        }

        private DataTable CreateDT()
        {
            DataTable t = new DataTable("p");
            t.Columns.Add("tm", typeof(int));
            t.Columns.Add("d", typeof(long));
            t.Columns.Add("t", typeof(DateTime));
            return t;
        }

        private string createInput()
        {
                string qs;
                if (IsNavigate)
                    qs = new MdoQueryString(HttpContext.Current.Request.QueryString).GetModuleParameters(TabModuleID, false);
                else
                    qs = XsltDbUtils.GetQSByClientID(this.ClientID).ToString(false);

                return string.Format(
                    @"<input type=""hidden"" id=""mdo-hid-{0}-value"" name=""mdo-hid-{0}-value"" value=""{1}"" />",
                    this.ClientID,
                    HttpUtility.HtmlAttributeEncode(qs));
        }

        private string createHtml(string html)
        {
            return string.Format("<div class=\"XsltDbMainDiv\" id=\"xsltdb-{0}\">{1}</div>", this.ClientID, html);
        }

        private static Regex CallableTypeRegex = new Regex("\"(.+?)\"", RegexOptions.Singleline | RegexOptions.Compiled);
        public string Transform(string xml, string xsl, bool IsSuper)
        {
            // Speedy hack
            var speedyHackToken = "##simple-config";
            if (xsl.StartsWith(speedyHackToken))
            {
                if (IsAjax||IsService)
                    return JustTransform(xsl.Substring(speedyHackToken.Length), xml, false, IsSuper);
                return createInput() +
                       createHtml(JustTransform(xsl.Substring(speedyHackToken.Length), xml, false, IsSuper));
            }
            // End of speedy hack

            var inlineJS = string.Empty;
            var handlerJS = string.Empty;

            var callable = HttpContext.Current.Request["XsltDbCallable"];

            var debug = xsl.Contains("##debug");
            if (debug)
                xsl = xsl.Replace("##debug", string.Empty).TrimStart();

            if (module == null && !string.IsNullOrEmpty(callable))
            {
                foreach (Match match in CallableRegex.Matches(xsl))
                    if (match.Groups[2].Value == callable)
                    {
                        if (match.Groups[4].Success)
                        {
                            var type = match.Groups[4].Value;
                            HttpContext.Current.Response.ContentType = CallableTypeRegex.Match(type).Groups[1].Value.Trim();
                        }
                        return JustTransform(match.Groups[5].Value, xml, debug, IsSuper).Trim();
                    }
                return string.Empty;
            }
            xsl = CallableRegex.Replace(xsl, delegate(Match match)
            {
                inlineJS += CreateHandlerJS(match.Groups[2].Value, match.Groups[3].Value, true);
                return string.Empty;
            });

            xsl = ClientXslRegex.Replace(xsl, delegate(Match match)
            {
                inlineJS += CreateTransformJS(match.Groups[1].Value, match.Groups[2].Value, match.Groups[3].Value);
                return string.Empty;
            });

            xsl = TemplateParserRegex.Replace(xsl, delegate(Match match)
            {
                templateParsers[match.Groups[1].Value] = match.Groups[2].Value;
                return string.Empty;
            });

            xsl = PreRenderRegex.Replace(xsl, string.Empty);
            xsl = ServiceRegex.Replace(xsl, string.Empty);
            xsl = SetupRegex.Replace(xsl, string.Empty);
            xsl = SearchableRegex.Replace(xsl, string.Empty);
            xsl = ImportableRegex.Replace(xsl, string.Empty);
            
            xsl = jsHandlerRegex.Replace(xsl, delegate(Match match)
            {
                xsl = jsHandlerRegex.Replace(xsl, string.Empty).TrimStart();
                var fn = match.Groups[1].Value.Trim();
                var argList = match.Groups[2].Value.Trim();
                handlerJS = CreateHandlerJS(fn, argList, false);
                return string.Empty;
            });
            xsl = svcContentTypeRegex.Replace(xsl, delegate(Match match)
            {
                if (HttpContext.Current != null && HttpContext.Current.Handler is IXsltDbService)
                    HttpContext.Current.Response.ContentType = match.Groups[1].Value;
                return string.Empty;
            });

            string liveHandler = string.Empty;
            bool liveSmart = false;
            xsl = liveModuleRegex.Replace(xsl, delegate(Match match) {
            if (match.Success)
                liveSmart = match.Groups[2].Success;
                liveHandler = createLiveHandler(
                    match.Groups[1].Success ? int.Parse(match.Groups[1].Value) : 10,
                    liveSmart
                    );
                return string.Empty;
            });

            if (IsAjax)
                xsl = HeaderRegex.Replace(xsl, string.Empty);

            var html = string.Empty;
            if (handlerJS.Length == 0 || IsAjax || IsService )
                html = JustTransform(xsl, xml, debug, IsSuper);

            var noajax = string.Empty;

            if (!IsAjax)
            {
                html = HeaderRegex.Replace(html, delegate(Match match)
                {
                    var p = (Page)(HttpContext.Current != null ? HttpContext.Current.Handler : null);
                    var attrs = GetXmlTagFromMatch(match);

                    var position = attrs.ContainsKey("position") ? attrs["position"] : "module";
                    var text = match.Groups["b"].Value;

                    if ( string.IsNullOrEmpty(text) )
                        return string.Empty;

                    if (attrs.ContainsKey("key"))
                    {
                        var key = attrs["key"];
                        var contextKey = "XsltDb.mdo:header." + key;
                        if (HttpContext.Current.Items.Contains(contextKey))
                            return string.Empty;
                        HttpContext.Current.Items[contextKey] = true;
                    }

                    if (position == "module")
                        noajax += text;

                    if (position == "page" && p != null)
                        p.Header.Controls.Add(new LiteralControl(text));

                    if (position == "form" && p != null)
                        p.Form.Controls.AddAt(0, new LiteralControl(text));

                    return string.Empty;
                });
            }

            html = html.Trim();

            if (html.Length > 0 && liveSmart)
            {
                var hash = html.MD5();
                var old_hash = XsltDbUtils.GetQSByClientID(this.ClientID)["@mdo-live-hash"];
                if (hash == old_hash && IsAjax)
                    return "mdo-live-nothing-changed-marker";

                html += string.Format("<span id=\"xsltdb-smart-live-vs-{0}\" state=\"{1}\" style=\"display:none;\"></span>",
                    ClientID, HttpUtility.HtmlAttributeEncode(html.MD5()));
            }

            if ( IsAjax || IsService )
                return html;

            //string qs;
            //if (IsNavigate)
            //    qs = new MdoQueryString(HttpContext.Current.Request.QueryString).GetModuleParameters(TabModuleID, false);
            //else
            //    qs = XsltDbUtils.GetQSByClientID(this.ClientID).ToString(false);

            var comm_html = @"
{handler}
{NOAJAX}
{HTML}
";
            var handler_html = @"
<script type=""text/javascript"">
/*<![CDATA[*/
{handler}
/*]]>*/
</script>
";
            if (handlerJS.Length == 0 && inlineJS.Length == 0 && html.Length == 0 && noajax.Length == 0 && liveHandler.Length == 0)
                return string.Empty;

            var completeJS = (liveHandler + handlerJS + inlineJS).Trim();
            if (completeJS.Length > 0)
                completeJS = handler_html.Replace("{handler}", completeJS);

            if (html.Length > 0 || noajax.Length > 0)
            {
                html = createHtml(html);

                return createInput() + comm_html
                .Replace("{handler}", completeJS)
                .Replace("{NOAJAX}", noajax)
                .Replace("{HTML}", html)
                ;
            }

            return completeJS;
        }

        public string ClientID { get { return BuildClientID(TrueHost, module); } }

        static Dictionary<string, int> globaIDs = new Dictionary<string, int>();
        public static string BuildClientID(Control TrueHost, PortalModuleBase module)
        {
            string gi;
            if (TrueHost != null)
            {
                if (module != null)
                    gi = TrueHost.ClientID + "_" + module.TabModuleId;
                else
                    gi = TrueHost.ClientID;
                if (!globaIDs.ContainsKey(gi))
                {
                    lock (globaIDs)
                    {
                        if (!globaIDs.ContainsKey(gi))
                            globaIDs[gi] = globaIDs.Count;
                    }
                }
                return "i" + globaIDs[gi];
            }
            else
                return HttpContext.Current.Request["XsltDbClientID"];
        }

        private string createLiveHandler(int seconds, bool smart)
        {
            Helper h = new Helper(this);
            string refresher = h.perform("ajax", null,
                    new string[] { "@mdo-live-refresh", "@mdo-live-hash" },
                    new string[] { smart ? "smart" : "simple", "@mdo_live_gethash(" + EncodeJsString(ClientID) + ")" },
                    false);
            return string.Format("setInterval({0}, {1});", EncodeJsString(refresher), seconds * 1000);
        }

        Dictionary<string, string>templateParsers = new Dictionary<string,string>();

        public string ApplyTemplate(string template, string parserName, string xml)
        {
            const string cdBegin = "<xsl:text disable-output-escaping=\"yes\"><![CDATA[";
            const string cdEnd = "]]></xsl:text>";
            const string cdataBegin = "__cdata_μ_begin__";
            const string cdataEnd = "__cdata_μ_end__";

            string templateParser = templateParsers[parserName];
            string key = template + ":" + templateParser;

            string xslt = (string)StaticCache2.Get(key, delegate() {
                template = template.Replace("<![CDATA[", cdataBegin).Replace("]]>", cdataEnd);

                templateParser = PrepareXslt(templateParser);

                var d = new XPathDocument(new StringReader(templateParser));
                var dnav = d.CreateNavigator();
                var e = dnav.Compile("//xsl:template[@name]");
                var man = new XmlNamespaceManager(dnav.NameTable);
                man.AddNamespace("xsl", "http://www.w3.org/1999/XSL/Transform");
                e.SetContext(man);

                var t = new XslCompiledTransform();

                var primaryParser = new Regex("\\[(.+?)\\]", RegexOptions.Singleline);

                var parsedTemplate = primaryParser.Replace(template, delegate(Match m)
                {
                    var rawTag = m.Groups[1].Value;
                    if (rawTag.ToLower().StartsWith("xml/"))
                    {
                        if (rawTag.Contains("<") || rawTag.Contains(">") || rawTag.Contains("\""))
                            throw new Exception("XML/XSL tags aren't allowed inside tag expression. Use pure XPath instead.");

                        var disallowed = new Regex("mdo:(.+?)\\(");
                        foreach (Match dm in disallowed.Matches(rawTag))
                            if (!"iif, coalesce, fmt-date, fmt-number, replace, split, match".Contains(dm.Groups[1].Value.Trim()))
                                throw new Exception("You can't use 'mdo:" + dm.Groups[1].Value.Trim() + "' extension in templates.");

                        return cdEnd + "{{current()/*" + rawTag.Substring(3) + "}}" + cdBegin;
                    }
                    foreach (XPathNavigator item in dnav.Select(e))
                    {
                        var name = item.SelectSingleNode("@name").Value;
                        var regexNavi = item.SelectSingleNode("regex");

                        string regex;
                        if (regexNavi != null)
                            regex = regexNavi.Value;
                        else
                            regex = name + "(:(?'p1'.+?))?(:(?'p2'.+?))?(:(?'p3'.+?))?(:(?'p4'.+?))?(:(?'p5'.+?))?";
                        var tr = new Regex("^" + regex + "$", RegexOptions.IgnoreCase);
                        var tm = tr.Match(rawTag);
                        if (tm.Success)
                        {
                            StringBuilder sb = new StringBuilder();
                            sb.Append(cdEnd);
                            sb.AppendFormat("<xsl:call-template name=\"{0}\">", name);
                            sb.Append("<xsl:with-param name=\"xml\" select=\"current()\"/>");
                            for (int i = 1; i < tm.Groups.Count; i++)
                            {
                                Group g = tm.Groups[i];
                                string gn = tr.GroupNameFromNumber(i);
                                if (g.Success && gn != i.ToString())
                                    sb.AppendFormat("<xsl:with-param name=\"{0}\">{1}</xsl:with-param>",
                                        tr.GroupNameFromNumber(i), XmlEncode(g.Value));
                            }
                            sb.Append("</xsl:call-template>");
                            sb.Append(cdBegin);
                            return sb.ToString();
                        }
                    }

                    return m.Value;
                });

                var tProcXsl = "<xsl:template match=\"/\">" + cdBegin + parsedTemplate + cdEnd + "</xsl:template>";

                var toXslt = new Regex("<regex>.*?</regex>", RegexOptions.Singleline);

                return toXslt.Replace(templateParser, string.Empty)
                    .Replace("</xsl:stylesheet>", tProcXsl + "</xsl:stylesheet>");
            });

            bool IsSuper = this.Config.IsSuper;

            return JustTransform(xslt, xml, false, IsSuper).Replace(cdataBegin, "<![CDATA[").Replace(cdataEnd, "]]>");
        }

        public static string XmlEncode(string s)
        {
            XmlDocument doc = new XmlDocument();
            doc.LoadXml("<root/>");
            doc.DocumentElement.InnerText = s;
            return doc.DocumentElement.InnerXml;
        }

        private static Regex cleaner = new Regex(@"<!\[CDATA\[.*?\]\]>|<mdo:([^ ^>]+?)[^>]*>.*?</mdo:\1[^>]*>|<!--.*?-->", RegexOptions.Singleline | RegexOptions.Compiled);
        public static int testFragment(string xsl, string fragment)
        {
            xsl = cleaner.Replace(xsl, delegate(Match match)
            {
                return string.Empty.PadLeft(match.Length, ' ');
            });
            Regex rx = new Regex(fragment);
            Match m = rx.Match(xsl);
            if (m.Success)
                return m.Index;
            return -1;
        }

        static readonly Regex DllRegEx = new Regex(@"##register-api\((?<assembly>[\w\.]+)\s*,\s*(?<class>[\w\.]+)\s*,\s*(?<prefix>[\w\.]+)\)\s*", RegexOptions.Singleline | RegexOptions.Compiled);
        public string PrepareXslt(string xsl)
        {
            var type = "text/xml";
            if (HttpContext.Current != null)
                type = HttpContext.Current.Response.ContentType;

            var method = "text";
            if (type.EndsWith("xml"))
                method = "xml";
            if (type.EndsWith("html") || type.EndsWith("javascript"))
                method = "html";

            var key = "xsltdb-prepare-xslt" + method + xsl.MD5();

            // TODO: Assembly referencer can't be just cached with processed XSLT as far as Api array must be created.
            // TODO: So the following code is not inside StaticCache2.Get method.
            xsl = DllRegEx.Replace(xsl, delegate(Match m)
            {
                var dll = new Dll
                {
                    AssemblyName = m.Groups["assembly"].Value,
                    ClassName = m.Groups["class"].Value,
                    Prefix = m.Groups["prefix"].Value
                };
                if (!Api.Any(d => d.Prefix == dll.Prefix))
                    Api.Add(dll);

                return string.Empty;
            });

            return (string)StaticCache2.Get(key, delegate()
            {
                xsl = ReplaceValueOf(xsl);

                var styleSheetIndex = testFragment(xsl, "<xsl:stylesheet|<xsl:transform");
                var templateIndex = testFragment(xsl, "<xsl:template");
                var outputIndex = testFragment(xsl, "<xsl:output");

                if (styleSheetIndex < 0)
                {
                    if (templateIndex < 0)
                    {
                        xsl = string.Format(xslLargeHeaderFooter, method, xsl, BuildApiPrefixes());
                    }
                    else
                    {
                        if (outputIndex < 0)
                        {
                            xsl =
                                xsl.Substring(0, templateIndex) +
                                string.Format(xslSmallHeaderOutput, method) +
                                xsl.Substring(templateIndex);
                        }
                        xsl = string.Format(xslSmallHeaderFooter, xsl, BuildApiPrefixes());

                    }
                }

                return xsl;
            });
        }

        private string BuildApiPrefixes()
        {
            var prefs = new List<string>();
            foreach (var dll in Api)
                prefs.Add(string.Format("xmlns:{0}=\"urn:{0}\"", dll.Prefix));
            return string.Join(" ", prefs.ToArray());
        }

        public const string xslCachePrefix = "xsl-transform-cache-key:";

        private string TransformRelease(string xml, string xsl, Helper h, string html, bool IsSuper)
        {
            var xslKey = xslCachePrefix + xsl.MD5();
            var t = (XslCompiledTransform)StaticCache2.Get(xslKey, delegate()
            {
                var transf = new XslCompiledTransform();
                var s = new XsltSettings(true, IsSuper);
                using (var sr = new StringReader(xsl))
                    using (var xr = XmlReader.Create(sr))
                        transf.Load(xr, s, new MdoResolver(this));
                return transf;
            });

            return DoTransform(xml, h, html, t);
        }

        private string DoTransform(string xml, Helper h, string html, XslCompiledTransform t)
        {
            using (var sr = new StringReader(xml))
            {
                using (var xr = XmlReader.Create(sr))
                {
                    using (var sw = new StringWriter())
                    {
                        using (var xw = XmlWriter.Create(sw, t.OutputSettings))
                        {
                            var xslArg = new XsltArgumentList();
                            xslArg.AddExtensionObject("urn:mdo", h);
                            foreach (var dll in Api)
                                xslArg.AddExtensionObject("urn:" + dll.Prefix, dll.Object);

                            t.Transform(xr, xslArg, xw, new MdoResolver(this));
                            xw.Flush();
                            sw.Flush();
                            html = sw.ToString();
                            if (t.OutputSettings.OutputMethod == XmlOutputMethod.Xml)
                            {
                                if (html.Trim().Length == 0)
                                    html = "<root/>";
                                var enc = "utf-8";
                                if (HttpContext.Current != null)
                                    enc = HttpContext.Current.Response.ContentEncoding.HeaderName;
                                html = string.Format("<?xml version=\"1.0\" encoding=\"{0}\"?>", enc) + html;
                            }
                        }
                    }
                }
            }
            return html;
        }

        private string CreateTempFile(string content, string ext)
        {
            var f = Path.Combine(
                DnnSettings.P.HomeDirectoryMapPath,
                Guid.NewGuid() + "." + ext);
            using (var sw = new StreamWriter(f))
                sw.Write(content);
            return f;
        }
        private string TransformDebug(string xml, string xsl, Helper h, string html, bool IsSuper)
        {
            var t = new XslCompiledTransform(true);
            var s = new XsltSettings(true, IsSuper);

            var xslFile = CreateTempFile(xsl, "xslt");
            var xmlFile = CreateTempFile(xml, "xml");

            try
            {
                t.Load(xslFile, s, new MdoResolver(this));

                using (var sw = new StringWriter())
                {
                    var xslArg = new XsltArgumentList();
                    xslArg.AddExtensionObject("urn:mdo", h);
                    foreach (var dll in Api)
                        xslArg.AddExtensionObject("urn:" + dll.Prefix, dll.Object);

                    t.Transform(xmlFile, xslArg, sw);
                    sw.Flush();
                    html = sw.ToString();
                }
                return html;
            }
            finally
            {
                File.Delete(xslFile);
                File.Delete(xmlFile);
            }
        }

        public class Dll
        {
            public object Object
            {
                get { return Activator.CreateInstance(AssemblyName, ClassName).Unwrap(); }
            }
            public string ClassName { get; set; }
            public string AssemblyName { get; set; }
            public string Prefix { get; set; }
        }

        public List<Dll> Api = new List<Dll>();

        static Regex DashesReplacerRegex = new Regex(@"mdo:[a-zA-Z0-9_]+\-[a-zA-Z0-9_\-]+\s*\(", RegexOptions.Compiled);
        static Regex CurlyRegex = new Regex(@"(?<=(^|>)[^<]*?)(?<o>\{+[hj]*?\{)(?<x>.*?)(?<c>\}\}+)", RegexOptions.Singleline | RegexOptions.Compiled);
        static Regex ExecuteRegex = new Regex("<\\s*xsl:execute\\s+select=\"([^\"]*)\"\\s*/\\s*>", RegexOptions.Singleline | RegexOptions.Compiled);
        static Regex LocalizationRegex = new Regex(@"#\s*([\w\.-]+)\s*/\s*([\w\.-]+)\s*#", RegexOptions.Compiled);
        static Regex EventsRegex = new Regex(@"(\#\#\$)([^\n^\r]*)", RegexOptions.Singleline | RegexOptions.Compiled);
        static Regex HeaderColonToPointRegex = new Regex(@"<\s*(/)?\s*mdo:header", RegexOptions.Singleline | RegexOptions.Compiled);

        private static string ReplaceValueOf(string xsl)
        {
            xsl = HeaderColonToPointRegex.Replace(xsl, "<$1mdo.header");
            xsl = EventsRegex.Replace(xsl, @"<span id=""{mdo:watch('$2')}"" style=""display:none;""></span>");
            xsl = DashesReplacerRegex.Replace(xsl, delegate(Match m)
            {
                return m.Value.Replace("-", "");
            });

            xsl = CurlyRegex.Replace(xsl, delegate(Match m)
            {
                string open = m.Groups["o"].Value;
                string xpath = m.Groups["x"].Value;
                string close = m.Groups["c"].Value;

                bool h = open.Contains("h");
                bool j = open.Contains("j");
                string openFull = open;

                if (h) open = open.Replace("h", "");
                if (j) open = open.Replace("j", "");

                int len = Math.Min(open.Length, close.Length);

                if (len > 2)
                    return openFull.Substring(1) + xpath + close.Substring(1);

                if (j) xpath = string.Format("mdo:jsencode({0})", xpath);

                return
                    string.Empty.PadLeft(open.Length - len, '{') +
                    "<xsl:value-of select=\"" + xpath + "\"" + (h ? " disable-output-escaping=\"yes\"" : "") + "/>" +
                    string.Empty.PadLeft(close.Length - len, '}');
            });

            xsl = ExecuteRegex.Replace(xsl, "<xsl:if test=\"$1\" />");

            xsl = LocalizationRegex.Replace(xsl, @"mdo:coalesce($$${1}/${2}/value[@culture=mdo:culture()], $$${1}/${2}/value[position()=1])");

            return xsl;
        }

        private string CreateHandlerJS(string fn, string argList, bool callable)
        {
            string js = @"
function {fn}({argList})
{
    var p = {};
    {argAssign}
    return mdo_handler_comm({ids}, p, {callable}, callback);
}
";
            string argAssign = string.Empty;
            argList = argList.Trim();
            if (argList.Length > 0)
            {
                string[] args = argList.Split(',');
                string[] assigns = new string[args.Length];

                for (int i = 0; i < args.Length; i++)
                {
                    args[i] = args[i].Trim();
                    assigns[i] = string.Format("p[\"{0}\"] = {0};", args[i]);
                }
                argAssign = string.Join(Environment.NewLine, assigns);
                argList = string.Join(", ", args).Trim();
            }
            if (argList.Length > 0) argList += ", ";
            argList += "callback";


            js = js
                .Replace("{fn}", fn.Trim())
                .Replace("{argList}", argList)
                .Replace("{argAssign}", argAssign.Trim())
                .Replace("{ids}", EncodeJsString(ClientID))
                .Replace("{callable}", callable ? "\"" + fn + "\"" : "null");


            return Regex.Replace(js, "\\s+", " ",RegexOptions.Singleline);
        }

        private string CreateTransformJS(string fn, string argList, string xsl)
        {
            string js = @"
function {fn}(xml{argList})
{
    var xsl={xsl};
    var p = {};
    {argAssign}
    return mdo_xslt_transform(xsl,xml,p);
}
";
            var argAssign = string.Empty;
            argList = argList.Trim();
            if (argList.Length > 0)
            {
                var args = argList.Split(',');
                var assigns = new string[args.Length];

                for (var i = 0; i < args.Length; i++)
                {
                    args[i] = args[i].Trim();
                    assigns[i] = string.Format("p[\"{0}\"] = {0};", args[i]);
                }
                argAssign = string.Join(Environment.NewLine, assigns);
                argList = ", " + string.Join(", ", args).Trim();
            }


            js = js
                .Replace("{fn}", fn.Trim())
                .Replace("{argList}", argList)
                .Replace("{argAssign}", argAssign.Trim())
                .Replace("{xsl}", EncodeJsString(PrepareXslt(xsl).Trim()));

            return js;
        }

        //public static string BuildActionModuleID(int TabID, int ModuleID, int TabModuleID)
        //{
        //    return "[" + TabID + "," + ModuleID + "," + TabModuleID + "]";
        //}

        public static string EncodeJsString(object s)
        {
            return EncodeJsString(s, true);
        }
        public static string EncodeJsString(object s, bool addQuotes)
        {
            if (s == null) s = string.Empty;
            string js = s.xJson();
            if (!addQuotes)
            {
                if (js.StartsWith("\""))
                    js.Substring(1);
                if (js.EndsWith("\""))
                    js.Substring(0, js.Length - 1);
            }
            return js;
        }
    }

    public class MdoQueryString : IEnumerable
    {
        NameValueCollection nvc;
        public MdoQueryString()
        {
            nvc = HttpUtility.ParseQueryString(string.Empty);
        }
        public MdoQueryString(NameValueCollection n) : this(n, string.Empty) { }
        public MdoQueryString(NameValueCollection n, string prefix)
        {
            nvc = HttpUtility.ParseQueryString(string.Empty);
            foreach (string key in NoEmpty(n))
                nvc[prefix + key] = n[key];
        }
        public MdoQueryString(string qs)
        {
            nvc = HttpUtility.ParseQueryString(string.Empty);
            NameValueCollection n = HttpUtility.ParseQueryString(qs);
            foreach (string key in NoEmpty(n))
                nvc[key] = n[key];
        }

        public object this[string pName, object dflt]
        {
            get
            {
                string pVal = this[pName];
                if (pVal == null || pVal.Length == 0)
                    return dflt;
                return pVal;
            }
        }
        public string this[string pName]
        {
            get { return nvc[pName]; }
            set { nvc[pName] = value; }
        }

        // Get{Global|Local}Parameters uses URL query string naming convension:
        //   - global parameters go as is
        //   - local module parameters go with -TabModuleID suffix
        private static Regex rParamTest = new Regex("^([^-]+)-(\\d+)$", RegexOptions.Compiled);
        public string GetGlobalParameters(bool includeCommands)
        {
            NameValueCollection n = HttpUtility.ParseQueryString(string.Empty);
            foreach (string key in NoEmpty(nvc))
            {
                string val = nvc[key];
                    if (!includeCommands && (key.StartsWith("@")))
                        continue;
                    if (!rParamTest.Match(key).Success)
                        n["$" + key] = val;
            }
            return n.ToString();
        }
        public string GetModuleParameters(int TabModuleID, bool includeCommands)
        {
            NameValueCollection n = HttpUtility.ParseQueryString(string.Empty);
            string tm = TabModuleID.ToString();
            foreach (string key in NoEmpty(nvc))
            {
                if (!includeCommands && (key.StartsWith("@")))
                    continue;
                Match m = rParamTest.Match(key);
                if (m.Success && m.Groups[2].Value == tm)
                    n[m.Groups[1].Value] = nvc[key];
            }
            return n.ToString();
        }

        // ToString uses module parameters naming convension
        //  - global parameters start with "$"
        //  - local parameters go as is
        public override string ToString()
        {
            return nvc.ToString();
        }

        public string ToString(bool includeCommands)
        {
            if (includeCommands)
                return this.ToString();

            NameValueCollection n = HttpUtility.ParseQueryString(string.Empty);
            foreach (string key in NoEmpty(nvc))
                if ((!key.StartsWith("@")) && (!key.StartsWith("$@")))
                    n[key] = nvc[key];
            return n.ToString();
        }

        internal void Append(string p)
        {
            NameValueCollection n = HttpUtility.ParseQueryString(p);
            foreach (string key in NoEmpty(n))
                nvc[key] = n[key];
        }

        public IEnumerator GetEnumerator()
        {
            return nvc.GetEnumerator();
        }

        public string[] AllKeys { get { return NoEmpty(nvc).AllKeys; } }

        private static NameValueCollection NoEmpty(NameValueCollection nvc)
        {
            NameValueCollection n = HttpUtility.ParseQueryString(string.Empty);
            foreach (string key in nvc)
            {
                if (!string.IsNullOrEmpty(key)) // key is null for http://somesite.com?nn
                {
                    string val = nvc[key];
                    if (!string.IsNullOrEmpty(val))
                        n[key] = nvc[key];
                }
            }
            return n;

        }

        internal string ParamsToUrl(bool includeCommands)
        {
            return ParamsToUrl(string.Empty, includeCommands);
        }
        internal string ParamsToUrl(string cli, bool includeCommands)
        {
            NameValueCollection n = HttpUtility.ParseQueryString(string.Empty);
            foreach (string key in NoEmpty(nvc))
            {
                if (includeCommands || ((!key.StartsWith("@")) && (!key.StartsWith("$@"))))
                {
                    if (key.StartsWith("$"))
                        n[key.Substring(1)] = nvc[key];
                    else if (cli.Length > 0)
                        n[key + "-" + cli] = nvc[key];
                }
            }

            return n.ToString();
        }

        internal void Remove(string p)
        {
            nvc.Remove(p);
        }
    }
    public class RedirectException : Exception
    {
        private string url;
        private bool transfer;
        public RedirectException()
        {
            url = Globals.NavigateURL();
            transfer = false;
        }
        public RedirectException(string url)
        {
            this.url = url;
            transfer = false;
        }
        public RedirectException(bool transfer)
        {
            url = Globals.NavigateURL();
            this.transfer = transfer;
        }
        public RedirectException(string url, bool transfer)
        {
            this.url = url;
            this.transfer = transfer;
        }
        public string URL { get { return url; } }
        public bool IsTransfer { get { return transfer; } }
    }

    public class MdoResolver : XmlResolver
    {
        XmlUrlResolver resolver = new XmlUrlResolver();
        Transformer transformer;
        public MdoResolver(Transformer t)
        {
            transformer = t;
        }
        public override ICredentials Credentials
        {
            set {
                resolver.Credentials = value;
            }
        }

        public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn)
        {
            if (absoluteUri.AbsoluteUri.StartsWith("~"))
            {
                return resolver.GetEntity(new Uri(
                    FileMan.MapPath(absoluteUri.AbsoluteUri)), role, ofObjectToReturn);
            }
            var modR = new Regex(@"mdo:ModuleID\s*\(\s*(\d+)\s*\)", RegexOptions.IgnoreCase | RegexOptions.Singleline);
            var m = modR.Match(absoluteUri.AbsoluteUri);
            if (m.Success)
            {
                var cnfg = XsltDbUtils.GetConfig(Convert.ToInt32(m.Groups[1].Value));
                var ms = new MemoryStream();
                using (var sw = new StreamWriter(ms))
                    sw.Write(transformer.PrepareXslt(cnfg.ActiveXslt));
                return new MemoryStream(ms.ToArray());
            }

            var modS = new Regex(@"mdo:import\s*\(\s*(.+?)\s*\)", RegexOptions.IgnoreCase | RegexOptions.Singleline);
            var s = modS.Match(absoluteUri.AbsoluteUri);
            if (s.Success)
            {
                var cnfg = XsltDbUtils.GetConfigByService(s.Groups[1].Value);
                var ms = new MemoryStream();
                using (var sw = new StreamWriter(ms))
                    sw.Write(transformer.PrepareXslt(cnfg.ActiveXslt));
                return new MemoryStream(ms.ToArray());
            }

            var modI = new Regex(@"mdo:import-?local\s*\(\s*(.+?)\s*\)", RegexOptions.IgnoreCase | RegexOptions.Singleline);
            var i = modI.Match(absoluteUri.AbsoluteUri);
            if (i.Success)
            {
                var cnfg = XsltDbUtils.GetConfig(transformer.DnnSettings.M.ModuleID, transformer.DnnSettings.T.TabID);
                foreach (Match match in Transformer.ImportableRegex.Matches(cnfg.ActiveXslt))
                {
                    if (match.Groups[1].Value == i.Groups[1].Value)
                    {
                        var ms = new MemoryStream();
                        using (var sw = new StreamWriter(ms))
                            sw.Write(transformer.PrepareXslt(match.Groups[2].Value));
                        return new MemoryStream(ms.ToArray());
                    }
                }
            }

            if (absoluteUri.AbsoluteUri.StartsWith("mdo:"))
            {
                using (var sr = new StreamReader(FileMan.MapPath("~/Portals/_default/mdo/" + absoluteUri.AbsoluteUri.Substring(4))))
                {
                    var content = sr.ReadToEnd();
                    content = transformer.PrepareXslt(content);
                    var ms = new MemoryStream();
                    using (var sw = new StreamWriter(ms))
                        sw.Write(content);
                    return new MemoryStream(ms.ToArray());
                }
            }
            return resolver.GetEntity(absoluteUri, role, ofObjectToReturn);
        }
    }

    public class StaticCache2
    {
        public delegate object Instantiator();
        public class Item
        {
            public object item;
            public int seconds = 60;
            public string[] tables;
            public SqlParameter[] parameters = null;
            public string privateConnection = null;
            public bool noCache = false;
            public SqlCacheDependency dep = null;
        }
        public static object Get(string key)
        {
            return CachingProvider.Instance().GetItem(key);
        }

        static Dictionary<string, string> syncLocks = new Dictionary<string,string>();
        public static object GetLock(string key)
        {
            string crc = key.MD5();
            lock(typeof(StaticCache2))
            {
                if ( !syncLocks.ContainsKey(crc) )
                    syncLocks.Add(crc,crc);
            }
            return syncLocks[crc];
        }
        public static object Get(string key, Instantiator callback)
        {
            return Get(key, 60, callback);
        }
        public static object Get(string key, int seconds, Instantiator callback)
        {
            object o = CachingProvider.Instance().GetItem(key);
            if (o != null)
                return o;

            lock (GetLock(key))
            {
                o = CachingProvider.Instance().GetItem(key);
                if (o != null)
                    return o;

                o = callback();

                if (o != null)
                {
                    if ( o is Item )
                    {
                        Item item = (Item)o;
                        if ( item.item == null )
                            return null;
                        if (item.noCache) return item.item;

                        if (item.dep != null)
                            HttpContext.Current.Cache.Insert(key, item.item, item.dep,
                                 DateTime.Now + TimeSpan.FromSeconds(item.seconds), Cache.NoSlidingExpiration);
                        else if (item.tables != null && item.tables.Length > 0)
                            HttpContext.Current.Cache.Insert(key, item.item,
                                 Data.CreateDependency(item.tables, item.privateConnection, item.parameters),
                                 DateTime.Now + TimeSpan.FromSeconds(item.seconds), Cache.NoSlidingExpiration);
                        else
                            CachingProvider.Instance().Insert(key, item.item, (DNNCacheDependency)null,
                                DateTime.Now + TimeSpan.FromSeconds(item.seconds), Cache.NoSlidingExpiration);
                        return item.item;
                    }
                    else
                        CachingProvider.Instance().Insert(key, o, (DNNCacheDependency)null,
                            DateTime.Now + TimeSpan.FromSeconds(seconds), Cache.NoSlidingExpiration);
                }

                return o;
            }
        }
        public static void Remove(string key)
        {
            lock (typeof(StaticCache2))
            {
                CachingProvider.Instance().Remove(key);
            }
        }
        public static void Clear(string prefix)
        {
            lock (typeof(StaticCache2))
            {
                List<string> keysToRemove = new List<string>();
                foreach (DictionaryEntry de in CachingProvider.Instance())
                    if (de.Key.ToString().StartsWith(prefix))
                        keysToRemove.Add(de.Key.ToString());
                foreach (string key in keysToRemove)
                    CachingProvider.Instance().Remove(key);
            }
        }
    }

    public class XPathContext : XsltContext
    {
        Dictionary<string, XPathVariable> vars = new Dictionary<string, XPathVariable>();
        public XPathContext()
            : base()
        {
        }

        public XPathContext(NameTable nt)
            : base(nt)
        {
        }

        public void AddVariable(string name, object value, XPathResultType type)
        {
            vars[name] = new XPathVariable(name, value, type);
        }
        public void AddVariable(XPathVariable v)
        {
            vars[v.Name] = v;
        }

        public override int CompareDocument(string baseUri, string nextbaseUri)
        {
            return 0;
        }

        public override bool PreserveWhitespace(System.Xml.XPath.XPathNavigator node)
        {
            return false;
        }

        public override IXsltContextFunction ResolveFunction(string prefix, string name, System.Xml.XPath.XPathResultType[] ArgTypes)
        {
            return null;
        }

        public override IXsltContextVariable ResolveVariable(string prefix, string name)
        {
            return vars[name];
        }

        public override bool Whitespace
        {
            get { return false; }
        }

    }

    public class XPathVariable : IXsltContextVariable
    {
        string name;
        object value;
        XPathResultType type;
        public XPathVariable(string name, object value, XPathResultType type)
        {
            this.name = name;
            this.value = value;
            this.type = type;
        }
        public string Name { get { return name; } }
        public object Evaluate(XsltContext xsltContext) { 
            return value;
        }
        public bool IsLocal { get { return true; } }
        public bool IsParam { get { return false; } }
        public XPathResultType VariableType { get { return type; } }

    }

    public class XsltDbPortalModuleBase : PortalModuleBase
    {
        public XsltDbPortalModuleBase()
            : base()
        {
            var control = LoadControl("~/DesktopModules/XsltDb/XsltDbHost.ascx");
            control.ID = "host";
            Controls.Add(control);
        }
    }
    public class XsltDbModuleSettingsBase : ModuleSettingsBase
    {
        IModuleSettingsControl settingsHost;
        public XsltDbModuleSettingsBase()
            : base()
        {
            var control = LoadControl("~/DesktopModules/XsltDb/SettingsHost.ascx");
            control.ID = "host";
            settingsHost = (IModuleSettingsControl)control;
            Controls.Add(control);
        }
        public override void LoadSettings()
        {
            settingsHost.LoadSettings();
            base.LoadSettings();
        }
        public override void UpdateSettings()
        {
            settingsHost.UpdateSettings();
            base.UpdateSettings();
        }
    }

    public interface IModuleSettingsControl
    {
        void LoadSettings();
        void UpdateSettings();
    }

    class XsltDbScheduledTask : SchedulerClient
    {
      public XsltDbScheduledTask(ScheduleHistoryItem objScheduleHistoryItem)
        : base()
      {
          ScheduleHistoryItem = objScheduleHistoryItem;
      }

      public override void DoWork()
      {
        try
        {
          // do some stuff ...
          Transformer t = new Transformer(-1, -1, -1);

          using (IDataReader dr = Data.ExecuteSQL(
            "SELECT s.scheduler_id AS id, s.scheduler_script AS sscript FROM Findy_XsltDb_Scheduler AS s " +
            "WHERE " +
            "  s.scheduler_next_start < GETDATE() AND s.scheduler_isrun = 0 OR " +
            "  s.scheduler_next_start < DATEADD(SECOND,s.scheduler_restart_sec,GETDATE()) AND s.scheduler_isrun = 1",
            null))
          {

            while (dr.Read())
            {
              string id = Convert.ToString(dr["id"]);
              string script = Convert.ToString(dr["sscript"]);

              Data.ExecuteSQL(
                "UPDATE Findy_XsltDb_Scheduler " +
                "SET scheduler_next_start = GETDATE(), scheduler_isrun = 1 " +
                "WHERE scheduler_id = " + id,
                null);

              t.JustTransform(script, "<root/>", false, true);

              Data.ExecuteSQL(
                "UPDATE Findy_XsltDb_Scheduler " +
                "SET scheduler_next_start = DATEADD(SECOND,scheduler_period_sec,scheduler_next_start), scheduler_isrun = 0 " +
                "WHERE scheduler_id = " + id,
                null);
            }

          }

          // then report success to the scheduler framework
          ScheduleHistoryItem.Succeeded = true;
        }

        // handle any exceptions
        catch (Exception exc)
        {
          // report a failure
          ScheduleHistoryItem.Succeeded = false;

          // log the exception into
          // the scheduler framework
          ScheduleHistoryItem.AddLogNote("EXCEPTION: " + exc.ToString());

          // call the Errored method
          Errored(ref exc);

          // log the exception into the DNN core
//          Exceptions.LogException(exc);
        }
      }
    }
}